﻿using CommunityToolkit.Mvvm.Input;
using FFmpeg.NET;
using Notifications.Wpf;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Input;
using VideoTools.Tasks;
using VideoTools.Util;
using VideoTools.vo;
using TaskStatus = VideoTools.vo.TaskStatus;

namespace VideoTools.Commands
{
    public class SplitVideoCommands : NotifyPropertyBase
    {
        private static volatile SplitVideoCommands _instance;
        private static readonly object SyncRoot = new object();
        static Dictionary<string, SplitVideoTaskItem> Task_Context =
            new Dictionary<string, SplitVideoTaskItem>();
        static Dictionary<string, IProgress<double>> Progress_Context =
            new Dictionary<string, IProgress<double>>();
        static string patternTime = @"^(\d{2}):(\d{2}):(\d{2})$"; // 正则表达式，匹配HH:mm:ss格式
        string patternNumber = @"^(\d{1,})$"; // 正则表达式，匹配HH:mm:ss格式

        public static SplitVideoCommands Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncRoot)
                    {
                        if (_instance == null)
                        {
                            _instance = new SplitVideoCommands();
                        }
                    }
                }

                return _instance;
            }
        }
        

        private SplitVideoCommands()
        {
            foreach (DeviceType dt in Utils.GetAllEnumValues<DeviceType>())
            {
                DeviceTypes.Add(dt);
            }
        }
        
        private DeviceType _DeviceType = DeviceType.GPU;
        public DeviceType DeviceType
        {
            get => _DeviceType;
            set => SetProperty(ref _DeviceType, value);
        }

        private bool _DeviceTypeEnable = true;

        public bool DeviceTypeEnable
        {
            get => _DeviceTypeEnable;
            set => SetProperty(ref _DeviceTypeEnable, value);
        }

        private ObservableCollection<DeviceType> _DeviceTypes =
            new ObservableCollection<DeviceType>();

        public ObservableCollection<DeviceType> DeviceTypes
        {
            get => _DeviceTypes;
            set => SetProperty(ref _DeviceTypes, value);
        }
        private bool isBatch = false;
        private bool _CompatibleModeEnabled = true;
        public bool CompatibleModeEnabled
        {
            get => _CompatibleModeEnabled;
            set => SetProperty(ref _CompatibleModeEnabled, value);
        }
        private bool _CompatibleModeIsChecked = false;
        public bool CompatibleModeIsChecked
        {
            get => _CompatibleModeIsChecked;
            set => SetProperty(ref _CompatibleModeIsChecked, value);
        }
        
        private bool _DoitButtonEnabled = true;

        public bool DoitButtonEnabled
        {
            get => _DoitButtonEnabled;
            set => SetProperty(ref _DoitButtonEnabled, value);
        }
        private bool _OpenVideoButtonEnabled = true;

        public bool OpenVideoButtonEnabled
        {
            get => _OpenVideoButtonEnabled;
            set => SetProperty(ref _OpenVideoButtonEnabled, value);
        }
        private bool _StartTimeEnabled = true;

        public bool StartTimeEnabled
        {
            get => _StartTimeEnabled;
            set => SetProperty(ref _StartTimeEnabled, value);
        }
        private bool _EndTimeEnabled = true;
        public bool EndTimeEnabled
        {
            get => _EndTimeEnabled;
            set => SetProperty(ref _EndTimeEnabled, value);
        }
        private bool _StopButtonEnabled = true;
        public bool StopButtonEnabled
        {
            get => _StopButtonEnabled;
            set => SetProperty(ref _StopButtonEnabled, value);
        }

        private bool _OnlyOneTabItemEnabled = true;
        public bool OnlyOneTabItemEnabled
        {
            get => _OnlyOneTabItemEnabled;
            set => SetProperty(ref _OnlyOneTabItemEnabled, value);
        }

        private SplitVideoTaskItem _CurrentTask;
        public SplitVideoTaskItem CurrentTask
        {
            get => _CurrentTask;
            set => SetProperty(ref _CurrentTask, value);

        }
        private bool _BatchTabItemEnabled = true;
        public bool BatchTabItemEnabled
        {
            get => _BatchTabItemEnabled;
            set => SetProperty(ref _BatchTabItemEnabled, value);
        }
        private Visibility _SpendTimeVisibility = Visibility.Hidden;

        public Visibility SpendTimeVisibility
        {
            get => _SpendTimeVisibility;
            set => SetProperty(ref _SpendTimeVisibility, value);
        }

        private Visibility _ProgressVisibility = Visibility.Hidden;

        public Visibility ProgressVisibility
        {
            get => _ProgressVisibility;
            set => SetProperty(ref _ProgressVisibility, value);
        }
        private bool _DeleteSelectedEnable = true;

        public bool DeleteSelectedEnable
        {
            get => _DeleteSelectedEnable;
            set => SetProperty(ref _DeleteSelectedEnable, value);
        }

        private bool _DeleteDoneEnable = true;

        public bool DeleteDoneEnable
        {
            get => _DeleteDoneEnable;
            set => SetProperty(ref _DeleteDoneEnable, value);
        }
        private bool _ClearListEnable = true;

        public bool ClearListEnable
        {
            get => _ClearListEnable;
            set => SetProperty(ref _ClearListEnable, value);
        }
        private bool _ShutDownOnDoneAll;

        public bool ShutDownOnDoneAll
        {
            get => _ShutDownOnDoneAll;
            set => SetProperty(ref _ShutDownOnDoneAll, value);
        }

        private ICommand _DoCommand;
        private ICommand _DoBatchCommand;
        private ICommand _OpenFileCommand;
        private ICommand _OpenBatchFileCommand;
        private ICommand _StopCommand;
        private ICommand _StopAllCommand;
        private ICommand _ChangeDeviceTypeCommand;
        private ICommand _DeleteSelectedCommand;
        private ICommand _DeleteDoneCommand;
        private ICommand _ClearListCommand;
        private ICommand _CopyTaskLogCommand;
        public ICommand ChangeDeviceTypeCommand =>
            _ChangeDeviceTypeCommand
            ?? (
                _ChangeDeviceTypeCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;

                    DeviceType = (DeviceType)o;
                })
            );
        public ICommand StopAllCommand =>
            _StopAllCommand
            ?? (
                _StopAllCommand = new RelayCommand<object>(o =>
                {
                    SplitVideoTaskQueueManager.Instance.StopAllTasks();
                    //ClearListEnable = true;
                    //DeviceTypeEnable = true;
                    //DoCommandEnable = true;
                    //DeleteSelectedEnable = true;
                    //Instance.CopyCommandEnable = true;
                })
            );
        public ICommand StopCommand =>
            _StopCommand
            ?? (
                _StopCommand = new RelayCommand<object>(o =>
                {
                    SplitVideoTaskQueueManager.Instance.StopAllTasks();
                    BatchTabItemEnabled = true;
                    OpenVideoButtonEnabled = true;
                    DeviceTypeEnable = true;
                    CompatibleModeEnabled = true;
                    StartTimeEnabled = true;
                    EndTimeEnabled = true;
                    DoitButtonEnabled = true;
                    StopButtonEnabled = false;
                    CurrentTask = null;
                    SpendTimeVisibility = Visibility.Hidden;
                    ProgressVisibility = Visibility.Hidden;
                })
            );
        public ICommand OpenFileCommand =>
            _OpenFileCommand
            ?? (
                _OpenFileCommand = new RelayCommand<object>(o =>
                {
                    System.Windows.Forms.OpenFileDialog openFileDialog =
                        new System.Windows.Forms.OpenFileDialog();
                    string filter = "视频文件(";
                    foreach (string allow in AppConstants.__allowVideoFiles)
                    {
                        filter += "*." + allow + ",";
                    }
                    filter = filter.Remove(filter.LastIndexOf(","));
                    filter += ")|";
                    foreach (string allow in AppConstants.__allowVideoFiles)
                    {
                        filter += "*." + allow + ";";
                    }
                    filter = filter.Remove(filter.LastIndexOf(";"));
                    openFileDialog.Filter = filter;
                    openFileDialog.Multiselect = false;
                    openFileDialog.RestoreDirectory = true;
                    openFileDialog.FilterIndex = 1;
                    string[] files = null;
                    if (openFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        files = openFileDialog.FileNames;
                    }
                    else
                    {
                        return;
                    }
                    for (int i = 0; i < files.Length; i++)
                    {
                        string f = files[i];
                        CurrentTask = GetTaskItem(f);
                        SplitVideoTaskQueueManager.Instance.ClearTasks();
                        SplitVideoTaskQueueManager.Instance.AddTask(
                            CurrentTask,
                            TaskAction,
                            TaskActionBefore,
                            TaskActionAfter
                        );


                    }
                })
            );
        public ICommand DoCommand =>
            _DoCommand
            ?? (
                _DoCommand = new RelayCommand<object>(o =>
                {
                    if (
                        SplitVideoTaskQueueManager
                            .Instance.Tasks.Where(t => t.Status != vo.TaskStatus.Completed)
                            .Count() == 0
                    )
                    {
                        return;
                    }
                    SplitVideoTaskQueueManager.Instance.StartProcessing();
                    
                })
            );
        private Action<CancellationToken, SplitVideoTaskItem> TaskActionAfter = (token, item) =>
        {
            //if (!item.Progress.ToString().Contains("100"))
            //{
            //    item.Path = "失败" + item.Path;
            //    var contentb = new NotificationContent()
            //    {
            //        Title = "视频截取失败" ,
            //        Type = NotificationType.Error,
            //        Message = item.Path
            //    };
            //    AppManager.Instance.NotificationManager.Show(contentb);
            //}
            //else
            //{
                var contentb = new NotificationContent()
                {
                    Title = "视频截取成功,点击打开",
                    Type = NotificationType.Information,
                    Message = item.Path
                };
                AppManager.Instance.NotificationManager.Show(contentb, onClick: () => Process.Start(AppConfig.Instance.VideoPlayerPath, item.OutPath));
            //}
            if (!Instance.isBatch)
            {
                SplitVideoTaskQueueManager.Instance.ClearTasks();
            }
        };
        private Action<CancellationToken, IProgress<double>, SplitVideoTaskItem> TaskAction = (
            token,
            progress,
            item
        ) =>
        {
            try
            {
                Process process = new Process();
                token.Register(() =>
                {
                    if (!process.HasExited)
                    {
                        item.Killed = true;
                        process.Kill();
                    }
                });
                process.StartInfo.FileName = "ffmpeg ";
                process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
                string param = item.Command;
                item.Info(AppConstants.DoubleClickMessage);
                item.Info("参数 ffmpeg " + param);
                process.StartInfo.Arguments = param;
                process.StartInfo.CreateNoWindow = true; //显示命令行窗口
                process.StartInfo.UseShellExecute = false;
                IDictionary<string, string?> environment = process.StartInfo.Environment;
                environment["taskItem_Path"] = item.Path;
                Task_Context.Add(item.Path, item);
                Progress_Context.Add(item.Path, progress);
                //将输出信息重定向
                process.StartInfo.RedirectStandardInput = true;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.EnableRaisingEvents = true;
                process.Exited += new EventHandler(scale_Exited);
                process.OutputDataReceived += new DataReceivedEventHandler(
                    scale_OutputDataReceived
                );
                process.ErrorDataReceived += new DataReceivedEventHandler(scale_ErrorDataReceived);
                process.Start();
                //开始异步读取输出
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                process.WaitForExit();
            }
            catch
            {
                item.Status = TaskStatus.Failed;
            }
            finally { }
        };

        private Action<CancellationToken, SplitVideoTaskItem> TaskActionBefore = (token, item) =>
        {
            Instance.BatchTabItemEnabled = false;
            Instance.OpenVideoButtonEnabled = false;
            Instance.DeviceTypeEnable = false;
            Instance.CompatibleModeEnabled = false;
            Instance.StartTimeEnabled = false;
            Instance.EndTimeEnabled = false;
            Instance.DoitButtonEnabled = false;
            Instance.StopButtonEnabled = true;
            Instance.SpendTimeVisibility = Visibility.Visible;
            Instance.ProgressVisibility = Visibility.Visible;
            item.Command = getParam(item);
        };

        private static string getParam(SplitVideoTaskItem item)
        {
            string p = item.Path;
            string path = System.IO.Path.GetDirectoryName(item.Path);

            FileInfo f = new FileInfo(p);
            string tempName = f.Name.Remove(f.Name.LastIndexOf("."));
            string param = "-ss " + item.StartTime;
            if (!string.IsNullOrEmpty(item.EndTime))
            {
                param += " -to " + item.EndTime;

            }
            param += " -accurate_seek -i \"" + p + "\"";
            if (Instance.DeviceType == DeviceType.GPU)
            {
                param += " -c:v h264_nvenc -gpu 0 ";
                if (Instance.CompatibleModeIsChecked)
                {
                    param += " -pix_fmt yuv420p ";
                }
            }
            else
            {
                param += " -c:v libx264 ";
            }
            string outPath = path + "\\" + tempName + AppConstants.SPLIT + ".mp4";
            item.OutPath = outPath;
            param += " -avoid_negative_ts 1 -y " + outPath;

            return param;
        }
        static void scale_OutputDataReceived(Object sender, DataReceivedEventArgs e) { }

        static void scale_ErrorDataReceived(Object sender, DataReceivedEventArgs e)
        {
            Process process = (Process)sender;
            string path = process.StartInfo.Environment["taskItem_Path"];
            SplitVideoTaskItem taskItem = Task_Context[path];
            IProgress<double> progress = Progress_Context[path];
            string info = e.Data;
            if (info == null)
            {
                return;
            }
            taskItem.Info(e.Data);
            foreach (string keyword in "frame,fps,size,time,bitrate,speed".Split(','))
            {
                if (!info.Contains(keyword))
                {
                    return;
                }
            }
            Match match = Regex.Match(info, "^frame.+time=(.+)bitrate=.+");
            if (match.Success)
            {
                string time = match.Groups[1].Value;
                if (time.Contains("."))
                {
                    time = time.Split(".")[0];
                }
                string[] times = time.Split(":");
                int cur =
                    int.Parse(times[0]) * 60 * 60 + int.Parse(times[1]) * 60 + int.Parse(times[2]);

                TimeSpan Duration = taskItem.Duration;
                int duration = (int)Duration.TotalSeconds;
                double rx = (Convert.ToDouble(cur) / Convert.ToDouble(duration));
                taskItem.Progress = rx;
                taskItem.Info("aa " + Convert.ToDouble(cur) + ",bb "  + Convert.ToDouble(duration));
                DateTime now = DateTime.Now;
                taskItem.SpendTime = new TimeSpan(now.Ticks - taskItem.taskStartTime.Ticks);
            }
        }

        static void scale_Exited(Object sender, EventArgs e)
        {
            Process process = (Process)sender;
            string path = process.StartInfo.Environment["taskItem_Path"];
            SplitVideoTaskItem taskItem = Task_Context[path];
            taskItem.Info("任务退出" + path);
            Task_Context.Remove(path);
            Progress_Context.Remove(path);
        }

        private SplitVideoTaskItem GetTaskItem(string f)
        {
            Engine ffmpeg = new Engine(@"ffmpeg.exe");
            InputFile inputFile = new InputFile(f);
            CancellationTokenSource source = new CancellationTokenSource();
            MetaData data = ffmpeg
                .GetMetaDataAsync(inputFile, source.Token)
                .GetAwaiter()
                .GetResult();
            if (data == null)
            {
                throw new Exception("视频有问题1");
            }
            CurrentTask = new SplitVideoTaskItem() { Path = f };
            CurrentTask.Status = TaskStatus.Pending;
            CurrentTask.AudioChannel = data.AudioData.ChannelOutput;
            int seconds = (int)data.Duration.TotalSeconds;
            CurrentTask.Duration = TimeSpan.FromSeconds(seconds);
            CurrentTask.StartTime = "00:00:00";
            CurrentTask.EndTime = CurrentTask.Duration.ToString(@"hh\:mm\:ss");
            if (data.VideoData != null)
            {
                StringBuilder sb = new StringBuilder();
                Random random = new Random();
                TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
                for (int i = 0; i < 10; i++)
                {
                    int index = random.Next(AppConstants.Chars.Length);
                    sb.Append(AppConstants.Chars[index]);
                }
                string currentImageFile =
                    "./"
                    + AppConstants.ImageFileStart
                    + Convert.ToInt64(ts.TotalMilliseconds).ToString()
                    + "_"
                    + sb.ToString()
                    + ".jpg";
                var outputFile = new OutputFile(currentImageFile);
                int durationMilliseconds = (int)data.Duration.TotalMilliseconds;
                ConversionOptions conversionOptions = new ConversionOptions()
                {
                    Seek = TimeSpan.FromMilliseconds(random.Next(1, durationMilliseconds - 1)),
                };
                ffmpeg
                    .GetThumbnailAsync(inputFile, outputFile, conversionOptions, source.Token)
                    .GetAwaiter()
                    .GetResult();
                Image image = Image.FromFile(currentImageFile);
                CurrentTask.FrameSize = image.Width + "x" + image.Height;
            }
            return CurrentTask;
        }

        public ICommand OpenBatchFileCommand =>
            _OpenBatchFileCommand
            ?? (
                _OpenFileCommand = new RelayCommand<object>(o =>
                {
                    System.Windows.Forms.OpenFileDialog openFileDialog =
                        new System.Windows.Forms.OpenFileDialog();
                    string filter = "批处理文本文件(";
                    filter += "*.txt";
                    filter += ")|*.txt";
                    openFileDialog.Filter = filter;
                    openFileDialog.RestoreDirectory = true;
                    openFileDialog.FilterIndex = 1;
                    string file = null;
                    List<SplitVideoTaskItem> tasks = new List<SplitVideoTaskItem>();
                    if (openFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        file = openFileDialog.FileName;
                        try
                        {
                            string[] lines = File.ReadAllLines(file);
                            foreach (string line in lines)
                            {
                                string[] infos = line.Split("|");
                                if (!(infos.Length == 2 || infos.Length == 3))
                                {
                                    var tips = new NotificationContent()
                                    {
                                        Title = "批处理文件格式错误",
                                        Type = NotificationType.Error,
                                        Message = "配置文件格式错误,每行一个任务，使用制表符分割\n第一列是文件路径\n第二列是开始时间00:00:00\n第三列是结束时间(可选)00:00:00\n" + line
                                    };
                                    AppManager.Instance.NotificationManager.Show(tips);
                                    tasks.Clear();
                                    return;
                                }

                                FileInfo fileInfo = new FileInfo(@infos[0]);
                                bool isVideo = false;
                                foreach (string ext in AppConstants.__allowVideoFiles)
                                {
                                    if (infos[0].EndsWith(ext))
                                    {
                                        isVideo = true;
                                    }
                                }
                                if (!isVideo)
                                {
                                    var tips = new NotificationContent()
                                    {
                                        Title = "不是视频文件(*.mkv,*.mp4,*.mov)",
                                        Type = NotificationType.Error,
                                        Message = infos[0]
                                    };
                                    AppManager.Instance.NotificationManager.Show(tips);
                                    tasks.Clear();
                                    return;
                                }

                                if (!fileInfo.Exists)
                                {
                                    var tips = new NotificationContent()
                                    {
                                        Title = "视频文件不存在",
                                        Type = NotificationType.Error,
                                        Message = infos[0]
                                    };
                                    AppManager.Instance.NotificationManager.Show(tips);
                                    tasks.Clear();
                                    return;
                                }
                                string startT = infos[1];

                                bool isMatch = Regex.IsMatch(startT, patternTime);
                                if (!isMatch)
                                {
                                    var tips = new NotificationContent()
                                    {
                                        Title = "开始时间格式不正确,正确格式00:01:23",
                                        Type = NotificationType.Error,
                                        Message = infos[0] + "\n" + startT
                                    };
                                    AppManager.Instance.NotificationManager.Show(tips);
                                    tasks.Clear();
                                    return;
                                }
                                string endT = null;

                                if (infos.Length == 3)
                                {
                                    endT = infos[2];
                                    isMatch = Regex.IsMatch(endT, patternTime);
                                    if (!isMatch)
                                    {
                                        if (!Regex.IsMatch(endT.Trim(), patternNumber))
                                        {
                                            var tips = new NotificationContent()
                                            {
                                                Title = "结束时间格式不正确",
                                                Type = NotificationType.Error,
                                                Message = "正确格式00:01:23或者数字1234\n" + endT
                                            };
                                            AppManager.Instance.NotificationManager.Show(tips);
                                            tasks.Clear();
                                            return;
                                        }
                                    }
                                }
                                SplitVideoTaskItem task = GetTaskItem(infos[0]);
                                task.StartTime = infos[1];
                                task.EndTime = endT;
                                tasks.Add(task);
                            }
                        }
                        catch (Exception eere)
                        {
                            var tips = new NotificationContent()
                            {
                                Title = "读取配置出错",
                                Type = NotificationType.Error,
                                Message = eere.Message  
                            };
                            AppManager.Instance.NotificationManager.Show(tips);
                            return;
                        }
                        finally
                        {
                        }
                    }
                    else
                    {
                        return;
                    }
                    isBatch = true;
                    if (tasks.Count > 0)
                    {
                        tasks.ForEach(t =>
                        {
                            SplitVideoTaskQueueManager.Instance.AddTask(
                            t,
                            TaskAction,
                            TaskActionBefore,
                            TaskActionAfter);
                        });
                    }
                })
            );
        

        public ICommand DoBatchCommand =>
            _DoBatchCommand
            ?? (
                _DoBatchCommand = new RelayCommand<object>(o =>
                {
                    if (
                        SplitVideoTaskQueueManager
                            .Instance.Tasks.Where(t => t.Status != TaskStatus.Completed)
                            .Count() == 0
                    )
                    {
                        return;
                    }
                    isBatch = true;
                    SplitVideoTaskQueueManager.Instance.StartProcessing();
                })
            );

        public ICommand DeleteSelectedCommand =>
            _DeleteSelectedCommand
            ?? (
                _DeleteSelectedCommand = new RelayCommand<object>(o =>
                {
                    SplitVideoTaskQueueManager.Instance.DeleteSelected();
                })
            );
        public ICommand ClearListCommand =>
            _ClearListCommand
            ?? (
                _ClearListCommand = new RelayCommand<object>(o =>
                {
                    SplitVideoTaskQueueManager.Instance.ClearTasks();
                })
            );

        public ICommand DeleteDoneCommand =>
            _DeleteDoneCommand
            ?? (
                _DeleteDoneCommand = new RelayCommand<object>(o =>
                {
                    SplitVideoTaskQueueManager.Instance.ClearCompletedTasks();
                })
            );
        public ICommand CopyTaskLogCommand =>
            _CopyTaskLogCommand
            ?? (
                _CopyTaskLogCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;
                    SplitVideoTaskItem model = (SplitVideoTaskItem)o;
                    Utils.CopyToClipboard(model.CopyLog(), "日志");
                })
            );
    }
}
